<?php

/**
 * View class.
 *
 * @version  $Id: view.php 2 2009-10-02 23:06:43Z perfilev $
 * @package  System
 */
 
class View
{
	/**
	 * @var  array  Array of global variables.
	 */
	public static $global = array();

	/**
	 * Returns a new View object.
	 *
	 * @param   string  $templat  View's template.
	 * @param   array   $data     Array of values.
	 * @return  this
	 */
	public static function factory($template = null, array $data = array())
	{
		return new View($template, $data);
	}

	/**
	 * @var  string  View's template.
	 */
	protected $template;

	/**
	 * @var  array  Array of local variables.
	 */
	protected $data;

	/**
	 * Sets the initial view's templare and local data.
	 *
	 * @param  string  $template  View's template.
	 * @param  array   $data      Array of values.
	 * @return void
	 */
	public function __construct($template = null, array $data = array())
	{
		$this->template = $template;
		$this->data = $data;
	}

	/**
	 * Magic method, searches for the given variable and returns its value.
	 * Local variables will be returned before global variables.
	 *
	 * @param   string $key  Variable name.
	 * @return  mixed
	 */
	public function __get($key)
	{
		if (array_key_exists($key, $this->data)) {
			return $this->data[$key];
		} elseif (array_key_exists($key, self::$global)) {
			return self::$global[$key];
		} else {
			return null;
		}
	}

	/**
	 * Magic method, calls set() with the same parameters.
	 *
	 * @param  string  $key    Variable name.
	 * @param  mixed   $value  Value.
	 * @return void
	 */
	public function __set($key, $value)
	{
		$this->set($key, $value);
	}

	/**
	 * Magic method, returns the output of render(). If any exceptions are
	 * thrown, the exception output will be returned instead.
	 *
	 * @return string
	 */
	public function __toString()
	{
		try {
			return $this->render();
		} catch (Exception $e) {
			return (string) $e;
		}
	}

	/**
	 * Assigns a variable by name.
	 *
	 * @param  string  $key    Variable name or an array of variables.
	 * @param  mixed   $value  Value.
	 * @return this
	 */
	public function set($key, $value = null)
	{
		if (is_array($key)) {
			foreach ($key as $name => $value) {
				$this->data[$name] = $value;
			}
		} else {
			$this->data[$key] = $value;
		}
		return $this;
	}

	/**
	 * Unset local variable.
	 *
	 * @param  string  $key  Variable name.
	 * @return void
	 */
	public function __unset($key)
	{
		unset($this->data[$key]);
	}

	/**
	 * Assigns a value by reference. The benefit of binding is that values can
	 * be altered without re-setting them. It is also possible to bind variables
	 * before they have values.
	 *
	 * @param   string  $key    Variable name.
	 * @param   mixed   $value  Referenced variable.
	 * @return  View
	 */
	public function bind($key, & $value)
	{
		$this->data[$key] =& $value;
		return $this;
	}

	/**
	 * Renders the view object to a string. Global and local data are merged
	 * and extracted to create local variables within the view file.
	 *
	 * Note: Global variables with the same key name as local variables will be
	 * overwritten by the local variable.
	 *
	 * @param  string  $template  View's template.
	 * @return string
	 * @throws Exception
	 */
	public function render($template = null)
	{
		if (null === $template) {
			$template = $this->template;
		}
		if (empty($template)) {
			throw new Exception('You must set the template to use within your view before rendering.');
		}
		if (false === $path = System::locate('views', $template . '.php')) {
			throw new Exception('Template "' . $template . '" does not exist.');
		}
		return View::capture($path, array_merge(self::$global, $this->data));
	}

	/**
	 * Captures the output that is generated when a view is included.
	 * The view data will be extracted to make local variables. This method
	 * is static to prevent object scope resolution.
	 *
	 * @param  string  $path  File path.
	 * @param  array   $data  Variables.
	 * @return string
	 */
	protected static function capture($path, array $data)
	{
		extract($data, EXTR_SKIP);
		ob_start();

		try {
			include $path;
		} catch (Exception $e) {
			ob_end_clean();
			throw $e;
		}

		return ob_get_clean();
	}
}
